using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Prometheus.TypeUtility;
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;
#else
using System.Windows;
using System.Windows.Markup;
#endif
namespace Prometheus.Binding.ReadOnly
{
    /// <summary>
    /// Contains list of read-only binding and process them using IMultiConverter
    /// </summary>
    [ContentProperty(
#if NETFX_CORE
        Name = 
#endif
"BindingWrappers")]
    public class proMultiBinding : proReadOnlyBinding
    {
        public proMultiBinding() : this(null)
        {
        }

        public proMultiBinding(string elementPropertyPath) : base(elementPropertyPath, null)
        {
            BindingWrappers = new List<proBindingWrapper>(2);
            Bindings = new ListWrapper<proReadOnlyBinding, proBindingWrapper>(BindingWrappers,
                                                                                      input => (proReadOnlyBinding) input.Binding);
        }

        public IMultiConverter MultiConverter { get; set; }

        public IEnumerable<proReadOnlyBinding> Bindings { get
            ; private set; }

        public List<proBindingWrapper> BindingWrappers { get; set; }

        public override FrameworkElement UIElement
        {
            get { return base.UIElement; }
            internal set
            {
                foreach (proReadOnlyBinding binding in Bindings)
                {
                    binding.UIElement = value;
                }
                base.UIElement = value;
            }
        }

        public override object GetValueForTarget()
        {
            if (MultiConverter == null)
                throw new InvalidOperationException("You must provide value for MultiConverter");

            object[] values = Bindings.Select(b => b.GetValueForTarget()).ToArray();
            object result = MultiConverter.Convert(values);
            return result;
        }

        internal override void Unbind()
        {
            foreach (proReadOnlyBinding binding in Bindings)
            {
                binding.FromDataProcessed -= UpdateOnChildrenDataContextChanged;

                binding.Unbind();
            }

            FromData();
        }

        /// <summary>
        /// To bind - we have to bind all bindings to provded UIElement
        /// </summary>
        public override void Bind()
        {
            foreach (proReadOnlyBinding binding in Bindings)
            {
                binding.ElementPropertyPath =
                    "The path does not exist. So binding cannot bring the value by FromData call";

                binding.FromDataProcessed += UpdateOnChildrenDataContextChanged;
                binding.Bind();
            }

            FromData();
        }

        private void UpdateOnChildrenDataContextChanged(object o, GenericEventArgs<object> genericEventArgs)
        {
            UpdateTarget();
        }
    }

    internal class ListWrapper<TReturn, TInput> : IEnumerable<TReturn>
    {
        private readonly Func<TInput, TReturn> _convert;
        private readonly IEnumerable<TInput> _inputCollection;

        public ListWrapper(IEnumerable<TInput> inputCollection, Func<TInput, TReturn> convert)
        {
            _inputCollection = inputCollection;
            _convert = convert;
        }

        #region IEnumerable<TReturn> Members

        public IEnumerator<TReturn> GetEnumerator()
        {
            IEnumerator<TInput> enumerator = _inputCollection.GetEnumerator();

            return new ListWrapperEnumerator<TReturn, TInput>(enumerator, _convert);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }

    internal class ListWrapperEnumerator<TReturn, TInput> : IEnumerator<TReturn>
    {
        private readonly Func<TInput, TReturn> _convert;
        private readonly IEnumerator<TInput> _inputEnumerator;

        public ListWrapperEnumerator(IEnumerator<TInput> inputEnumerator, Func<TInput, TReturn> convert)
        {
            _inputEnumerator = inputEnumerator;
            _convert = convert;
        }

        #region IEnumerator<TReturn> Members

        public void Dispose()
        {
            _inputEnumerator.Dispose();
        }

        public bool MoveNext()
        {
            return _inputEnumerator.MoveNext();
        }

        public void Reset()
        {
            _inputEnumerator.Reset();
        }

        public TReturn Current
        {
            get { return _convert(_inputEnumerator.Current); }
        }

        object IEnumerator.Current
        {
            get { return Current; }
        }

        #endregion
    }
}